﻿//Collects parameters during a select
var $dbParams = function () {
    this.command = ""; //s = select | d = delete | c = count
    this.collection = "";
    this.fields = [];
    this.operator = null;
    this.orderbyfields = [];
    this.reverseorder = false;
    this.topcount = -1;
};

var $db = new function () {
    //sortType: 0 = nativeSort; 1 = quickSort;
    this.sortType = 0;
    this.objectByRef = false;

    this.op = {

        "and": function (operatora, operatorb) {
            return new function () {
                var _operatora = operatora;
                var _operatorb = operatorb;
                this.check = function (row) {
                    return (_operatora.check(row) && _operatorb.check(row));
                };
            };
        },

        "or": function (operatora, operatorb) {
            return new function () {
                var _operatora = operatora;
                var _operatorb = operatorb;
                this.check = function (row) {
                    return (_operatora.check(row) || _operatorb.check(row));
                };
            };
        },

        "equal": function (field, value) {
            return new function () {
                var _field = field;
                var _value = value;
                this.check = function (row) {
                    if (typeof (row[_field]) == "string" && typeof (_value) == "string")
                        return row[_field].toLowerCase() == _value.toLowerCase();
                    else
                        return row[_field] == _value;
                };
            };
        },

        "notequal": function (field, value) {
            return new function () {
                var _field = field;
                var _value = value;
                this.check = function (row) {
                    if (typeof (row[_field]) == "string" && typeof (_value) == "string")
                        return row[_field].toLowerCase() != _value.toLowerCase();
                    else
                        return row[_field] != _value;
                };
            };
        },

        "greaterthan": function (field, value) {
            return new function () {
                var _field = field;
                var _value = value;
                this.check = function (row) {
                    if (typeof (row[_field]) == "string" && typeof (_value) == "string")
                        return row[_field].toLowerCase() > _value.toLowerCase();
                    else
                        return row[_field] > _value;
                };
            };
        },

        "lessthan": function (field, value) {
            return new function () {
                var _field = field;
                var _value = value;
                this.check = function (row) {
                    if (typeof (row[_field]) == "string" && typeof (_value) == "string")
                        return row[_field].toLowerCase() < _value.toLowerCase();
                    else
                        return row[_field] < _value;
                };
            };
        },

        "contains": function (field, value) {
            return new function () {
                var _field = field;
                var _value = value;
                this.check = function (row) {
                    if (typeof (row[_field]) == "string" && typeof (_value) == "string")
                        return row[_field].toLowerCase().indexOf(_value.toLowerCase()) > -1;
                    else
                        return false;
                };
            };
        },


        "null": null

        /*
        GreaterEqual,
        LessEqual,
        Contains,
        NotContains,
        BeginsWith,
        EndsWith,
        Like,
        NotLike,
        In,
        NotIn,
        Between,
        NotBetween,
        Null,
        NotNull,
        Yesterday,
        Today,
        Tomorrow,
        Last7Days,
        Next7Days,
        LastWeek,
        ThisWeek,
        NextWeek,
        LastMonth,
        ThisMonth,
        NextMonth,
        On,
        OnOrBefore,
        OnOrAfter,
        LastYear,
        ThisYear,
        NextYear,
        LastXHours,
        NextXHours,
        LastXDays,
        NextXDays,
        LastXWeeks,
        NextXWeeks,
        LastXMonths,
        NextXMonths,
        LastXYears,
        NextXYears,
        */
    };

    var db = {};

    var jsdbcollection = function (collection) {
        this.name = collection;
        var _array = [];
        var _delarray = [];

        this.addvalue = function (value) {
            _array.push(value);
            //_array[_length++] = value;
            //Array.prototype.push.apply(_array, [value]);
        };

        //this.select = function (count, fields, operator, orderbyfields, reverseorder) {
        this.run = function (dbParams) {
            var result = [];
            var recordcount = 0;

            //Se è indicato il parametro top e non ci sono nè ordinamenti nè where limita subito l'array al numero di record richiesti
            if (dbParams.topcount > 0 && dbParams.orderbyfields.length == 0 && dbParams.operator == null)
                _topcount = Math.min(dbParams.topcount, _array.length);
            else
            //altrimenti deve analizzare tutto l'array prima di selezionare i primi x record
                _topcount = _array.length;

            //TODO: anche nel caso della delete senza criteri si può svuotare l'array con un solo comando

            //Se non ci sono campi, criteri di ricerca o ordinamento il risultato può essere generato con una sola riga
            if ((dbParams.command != "s" || (dbParams.fields.length == 0 && dbParams.orderbyfields.length == 0)) && dbParams.operator == null) {
                switch (dbParams.command) {
                    case "c":
                        //Restituisce il conteggio di tutti i record
                        recordcount = _topcount;
                        break;
                    case "d":
                        //Cancella tutti i record!
                        recordcount = _topcount;
                        _array = [];
                        break;
                    case "s":
                        //Restituisce tutti i record indicati da _topcount
                        result = _array.clone(_topcount);
                        break;
                }
            }
            else {
                //Cicla sull'array per applicare la clausola where e per aggiungere i campi richiesti dalla select
                for (row = 0; row < _topcount; row++) {
                    //Clausola where
                    if (dbParams.operator == null || dbParams.operator.check(_array[row])) {

                        switch (dbParams.command) {
                            case "s":
                                //Aggiunge le righe solo nel caso di una select
                                //Aggiunge i campi
                                var obj = {};

                                //Se non sono indicati aggiunge tutta la riga
                                if (dbParams.fields.length == 0) {
                                    if ($db.objectByRef)
                                        result.push(_array[row]);
                                    else
                                        result.push(_array[row].clone());
                                }
                                //Se sono indicati dei campi aggiunge solo quelli
                                else {
                                    for (x = 0; x < dbParams.fields.length; x++) {
                                        var field = dbParams.fields[x];
                                        if (_array[row][field]) {
                                            obj[field] = _array[row][field];
                                        }
                                    }
                                    if ($db.objectByRef)
                                        result.push(obj);
                                    else
                                        result.push(obj.clone());
                                }
                                break;

                            //delete        
                            case "d":
                                
//                                                                _array.splice(row, 1);
//                                                                row--;
//                                                                _topcount--;
                                _delarray.push(row);
                                break;
                        }

                        recordcount++;
                    }
                }

                //Cancella le rige 
                if (dbParams.command == "d")
                    for (var drow = 0; drow < _delarray.length; drow++)
                        _array.splice(drow, 1);


                //Ordinamento. Eseguito solo se è una select e se ci sono almeno 2 risultati
                if (dbParams.command == "s" && recordcount > 1 && dbParams.orderbyfields.length > 0) {
                    switch ($db.sortType) {
                        case 0:
                            nativesort(result, dbParams.orderbyfields, dbParams.reverseorder);

                            break;
                        case 1:
                            result = quickSort(result, dbParams.orderbyfields, dbParams.reverseorder);
                            break;
                    }
                }

                //Se i record da restituire sono più di zero taglia l'array in base all'indicazione della clausola top
                //La clausola top esiste solo per la funziona select. Non serve quindi controllare il comando
                if (dbParams.topcount > 0) {
                    if ($db.objectByRef)
                        result = result.slice(0, dbParams.topcount);
                    else
                        result = result.clone(dbParams.topcount);
                }

                
            }

            

            //Se non è select restituisce solo il conteggio dei record
            if (dbParams.command != "s")
                return recordcount;
            else
                return result;
        };
    }


    /////////////////////////////////////////////
    // SELECT

    //Funzione select
    this.select = function (fields) {
        return new jsdbselect(arguments);
    };

    //Classe select
    var jsdbselect = function () {
        var _dbParams = new $dbParams();
        _dbParams.command = "s";
        _dbParams.fields = (function () {
            var result = [];

            //Se sono stati specificati campi li aggiunge all'array
            if (arguments[0][0].length > 0) {
                for (arg = 0; arg < arguments[0][0].length; arg++) {
                    result.push(arguments[0][0][arg]);
                }
            }
            return result;
        } (arguments));

        this.from = function (collection) {
            _dbParams.collection = collection;
            return new jsdbfrom(_dbParams);
        }

        this.top = function (topcount) {
            _dbParams.topcount = topcount;
            return new jsdbtop(_dbParams);
        }
    };

    //Classe jsdbtop
    var jsdbtop = function (_dbParams) {
        this.from = function (collection) {
            _dbParams.collection = collection;
            return new jsdbfrom(_dbParams);
        }
    }


    //Classe from
    var jsdbfrom = function (_dbParams) {
        this.execute = function () {
            return db[_dbParams.collection].run(_dbParams);
        };

        this.where = function (operator) {
            _dbParams.operator = operator;
            return new jsdbwhere(_dbParams);
        };

        this.orderby = function (orderbyfields) {
            return new jsdborderby(_dbParams, arguments);
        };
    };


    //Classe where
    var jsdbwhere = function (_dbParams) {
        this.execute = function () {
            return db[_dbParams.collection].run(_dbParams);
        };

        this.orderby = function (orderbyfields) {
            return new jsdborderby(_dbParams, arguments);
        };
    };


    //Classe orderby
    var jsdborderby = function (_dbParams, orderbyfields) {
        _dbParams.orderbyfields = (function () {
            var result = [];

            //Se sono stati specificati campi li aggiunge all'array
            if (arguments[0].length > 0) {
                for (arg = 0; arg < arguments[0].length; arg++) {
                    result.push(arguments[0][arg]);
                }
            }
            return result;
        } (orderbyfields));

        this.execute = function () {
            return db[_dbParams.collection].run(_dbParams);
        };

        this.desc = function () {
            return new jsdbdesc(_dbParams);
        };
    };


    //Classe orderby desc
    var jsdbdesc = function (_dbParams) {
        this.execute = function () {
            _dbParams.reverseorder = true;
            return db[_dbParams.collection].run(_dbParams);
        };
    };


    /////////////////////////////////////////////
    // COUNT

    //Funzione count
    this.count = function () {
        var _dbParams = new $dbParams();
        _dbParams.command = "c";
        return new jsdbcount(_dbParams);
    };

    //Classe jsdbcount
    var jsdbcount = function (_dbParams) {
        this.from = function (collection) {
            _dbParams.collection = collection;
            return new jsdbfrom(_dbParams);
        }
    };

    /////////////////////////////////////////////
    // INSERT

    //Funzione insert
    this.insert = function () {
        return new jsdbinsert();
    }

    //Classe jsdbinsert
    var jsdbinsert = function () {
        this.into = function (collection) {
            return new jsdbinto(collection);
        }
    };

    //Classe jsdbinsertcollection
    var jsdbinto = function (collection) {
        this.value = function (value) {
            db[collection].addvalue(value);
        };
    }

    /////////////////////////////////////////////
    // DELETE

    //funzione delete
    this.del = function () {
        return new jsdbdelete();
    };

    var jsdbdelete = function () {
        var _dbParams = new $dbParams();
        _dbParams.command = "d";

        this.from = function (collection) {
            _dbParams.collection = collection;
            return new jsdbfrom(_dbParams);
        };
    };



    /////////////////////////////////////////////
    // CREATE collection

    //Classe create collection
    this.create = {};
    this.create.collection = function (collection) {
        db[collection] = new jsdbcollection(collection);
    };


    /////////////////////////////////////////////
    // DELETE COLLECTION
    this.deletecollection = function (collection) {
        delete db[collection];
    };



    /////////////////////////////////////////////
    // ALTRO

    this.getdb = function () {
        return db;
    }
};

var quickSort = function (arr, orderbyfields, reverse) {
    if (arr.length <= 1) {
        return arr;
    };

    //Corrisponde a Math.floor
    var pivot = arr.splice((arr.length / 2) & 0xFFFFFFFF, 1)[0];
    var left = [];
    var right = [];

    for (var i = 0; i < arr.length; i++) {
        for (var f = 0; f < orderbyfields.length; f++) {
            var nameA = arr[i][orderbyfields[f]];
            var nameP = pivot[orderbyfields[f]];

            if (typeof (nameA) == "string")
                nameA = nameA.toLowerCase();

            if (typeof (nameP) == "string")
                nameP = nameP.toLowerCase();

            if (nameA == nameP) {
                if (f == orderbyfields.length - 1) {
                    if (reverse) left.push(arr[i])
                    else right.push(arr[i]);
                    break;
                }
            }
            else if (nameA < nameP) {
                if (reverse) right.push(arr[i]);
                else left.push(arr[i]);
                break;
            }
            else {
                if (reverse) left.push(arr[i]);
                else right.push(arr[i]);
                break;
            }
        }
    }

    var xleft = quickSort(left, orderbyfields, reverse);
    var xright = quickSort(right, orderbyfields, reverse);

    Array.prototype.push.apply(xleft, [pivot]);
    Array.prototype.push.apply(xleft, xright);
    return xleft;
};


var nativesort = function (arr, orderbyfields, reverse) {
    arr.sort(function (a, b) {
        for (f = 0; f < orderbyfields.length; f++) {
            var nameA = a[orderbyfields[f]];
            var nameB = b[orderbyfields[f]];

            if (typeof (nameA) == "string")
                nameA = nameA.toLowerCase();

            if (typeof (nameB) == "string")
                nameB = nameB.toLowerCase();

            if (nameA < nameB)
                if (reverse) return 1;
                else return -1;
            else if (nameA > nameB)
                if (reverse) return -1;
                else return 1;
        }

        return 0;
    });
};

//TODO: delete
//TODO: update
//TODO: aggiungere gli altri operatori
//TODO: join
//TODO: transazioni - commit - rollback
//TODO: indici
//TODO: salvataggio su localStorage
//TODO: lettura da localStorage
//TODO: lettura paginata

//Clona effettivamente gli oggetti di un array che altrimenti sarebbero "copiati" per riferimento e non per valore
Object.prototype.clone = function (arraylength) {
    var newObj;

    //Se è un array cicla sui suoi elementi
    if (this instanceof Array) {
        newObj = [];
        for (var i = 0; i < (arraylength || this.length); i++) {
            if (typeof this[i] == "object") {
                newObj[i] = this[i].clone();
            } else newObj[i] = this[i]
        }
    }
    //Se è un oggetto cicla sulle sue proprietà
    else {
        newObj = {};
        for (var i in this) {
            if (i == 'clone') continue;
            if (this[i] && typeof this[i] == "object") {
                newObj[i] = this[i].clone();
            } else newObj[i] = this[i]
        }
    }

    return newObj;
};